#ifndef GRAPHBUILDER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define GRAPHBUILDER_H_62B23520_7C8E_11DE_8A39_0800200C9A66

#if defined(_MSC_VER) ||                                            \
    (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \
     (__GNUC__ >= 4)) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif

#include "yaml-cpp/mark.h"
#include <string>

namespace YAML {
class Parser;

// GraphBuilderInterface
// . Abstraction of node creation
// . pParentNode is always nullptr or the return value of one of the NewXXX()
//   functions.
class GraphBuilderInterface {
public:
    virtual ~GraphBuilderInterface() = 0;

    // Create and return a new node with a null value.
    virtual void* NewNull(const Mark& mark, void* pParentNode) = 0;

    // Create and return a new node with the given tag and value.
    virtual void* NewScalar(const Mark& mark, const std::string& tag,
                            void* pParentNode, const std::string& value) = 0;

    // Create and return a new sequence node
    virtual void* NewSequence(const Mark& mark, const std::string& tag,
                              void* pParentNode) = 0;

    // Add pNode to pSequence.  pNode was created with one of the NewXxx()
    // functions and pSequence with NewSequence().
    virtual void AppendToSequence(void* pSequence, void* pNode) = 0;

    // Note that no moew entries will be added to pSequence
    virtual void SequenceComplete(void* pSequence) { (void)pSequence; }

    // Create and return a new map node
    virtual void* NewMap(const Mark& mark, const std::string& tag,
                         void* pParentNode) = 0;

    // Add the pKeyNode => pValueNode mapping to pMap.  pKeyNode and pValueNode
    // were created with one of the NewXxx() methods and pMap with NewMap().
    virtual void AssignInMap(void* pMap, void* pKeyNode, void* pValueNode) = 0;

    // Note that no more assignments will be made in pMap
    virtual void MapComplete(void* pMap) { (void)pMap; }

    // Return the node that should be used in place of an alias referencing
    // pNode (pNode by default)
    virtual void* AnchorReference(const Mark& mark, void* pNode) {
        (void)mark;
        return pNode;
    }
};

// Typesafe wrapper for GraphBuilderInterface.  Assumes that Impl defines
// Node, Sequence, and Map types.  Sequence and Map must derive from Node
// (unless Node is defined as void).  Impl must also implement function with
// all of the same names as the virtual functions in GraphBuilderInterface
// -- including the ones with default implementations -- but with the
// prototypes changed to accept an explicit Node*, Sequence*, or Map* where
// appropriate.
template <class Impl>
class GraphBuilder : public GraphBuilderInterface {
public:
    typedef typename Impl::Node Node;
    typedef typename Impl::Sequence Sequence;
    typedef typename Impl::Map Map;

    GraphBuilder(Impl& impl) : m_impl(impl) {
        Map* pMap = nullptr;
        Sequence* pSeq = nullptr;
        Node* pNode = nullptr;

        // Type consistency checks
        pNode = pMap;
        pNode = pSeq;
    }

    GraphBuilderInterface& AsBuilderInterface() { return *this; }

    virtual void* NewNull(const Mark& mark, void* pParentNode) {
        return CheckType<Node>(m_impl.NewNull(mark, AsNode(pParentNode)));
    }

    virtual void* NewScalar(const Mark& mark, const std::string& tag,
                            void* pParentNode, const std::string& value) {
        return CheckType<Node>(
            m_impl.NewScalar(mark, tag, AsNode(pParentNode), value));
    }

    virtual void* NewSequence(const Mark& mark, const std::string& tag,
                              void* pParentNode) {
        return CheckType<Sequence>(
            m_impl.NewSequence(mark, tag, AsNode(pParentNode)));
    }
    virtual void AppendToSequence(void* pSequence, void* pNode) {
        m_impl.AppendToSequence(AsSequence(pSequence), AsNode(pNode));
    }
    virtual void SequenceComplete(void* pSequence) {
        m_impl.SequenceComplete(AsSequence(pSequence));
    }

    virtual void* NewMap(const Mark& mark, const std::string& tag,
                         void* pParentNode) {
        return CheckType<Map>(m_impl.NewMap(mark, tag, AsNode(pParentNode)));
    }
    virtual void AssignInMap(void* pMap, void* pKeyNode, void* pValueNode) {
        m_impl.AssignInMap(AsMap(pMap), AsNode(pKeyNode), AsNode(pValueNode));
    }
    virtual void MapComplete(void* pMap) { m_impl.MapComplete(AsMap(pMap)); }

    virtual void* AnchorReference(const Mark& mark, void* pNode) {
        return CheckType<Node>(m_impl.AnchorReference(mark, AsNode(pNode)));
    }

private:
    Impl& m_impl;

    // Static check for pointer to T
    template <class T, class U>
    static T* CheckType(U* p) {
        return p;
    }

    static Node* AsNode(void* pNode) { return static_cast<Node*>(pNode); }
    static Sequence* AsSequence(void* pSeq) {
        return static_cast<Sequence*>(pSeq);
    }
    static Map* AsMap(void* pMap) { return static_cast<Map*>(pMap); }
};

void* BuildGraphOfNextDocument(Parser& parser,
                               GraphBuilderInterface& graphBuilder);

template <class Impl>
typename Impl::Node* BuildGraphOfNextDocument(Parser& parser, Impl& impl) {
    GraphBuilder<Impl> graphBuilder(impl);
    return static_cast<typename Impl::Node*>(
        BuildGraphOfNextDocument(parser, graphBuilder));
}
} // namespace YAML

#endif // GRAPHBUILDER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
